package ca.tatham.senssim;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import ca.tatham.senssim.util.Address;
import ca.tatham.senssim.util.Counter;

public abstract class AbstractNode implements Node
{
  private static final int ENERGY_PER_BYTE_SENT = 1;
  private static final int ENERGY_PER_BYTE_RECEIVED = 1;
  private final Address m_address;
  private final List<Link> m_links = new LinkedList<Link>();
  private final Counter m_bytesReceived = new Counter();
  private final Counter m_bytesSent = new Counter();

  private final Counter m_energyConsumed = new Counter();
  private int m_energyRemaining;
  private int m_timeOfDeath;

  protected AbstractNode(Address address, int energy)
  {
    m_address = address;
    m_energyRemaining = energy;
    s_allNodes.add(this);
  }

  protected abstract void processPacket(Packet packet);

  protected void log(String msg)
  {
    Simulation
        .log(getClass().getSimpleName() + " " + getAddress() + ": " + msg);
  }

  public void addLink(Link link)
  {
    m_links.add(link);
  }

  public final Address getAddress()
  {
    return m_address;
  }

  public final void receivePacket(Packet packet)
  {
    if (canReceivePacket(packet))
    {
      m_bytesReceived.increment(packet.getSize());
      m_energyConsumed.increment(packet.getSize() * ENERGY_PER_BYTE_RECEIVED);
      if (m_address.matches(packet.getDestination())
          && !m_address.equals(packet.getSource()))
      {
        processPacket(packet);
      }
    }
  }

  public boolean sendPacket(Packet packet)
  {
    if (canSendPacket(packet))
    {
      m_bytesSent.increment(packet.getSize());
      m_energyConsumed.increment(packet.getSize() * ENERGY_PER_BYTE_SENT);
      for (Link link : m_links)
      {
        link.sendPacket(packet);
      }
      return true;
    }
    return false;
  }

  private boolean canSendPacket(Packet packet)
  {
    if (isAlive())
    {
      m_energyRemaining -= packet.getSize() * ENERGY_PER_BYTE_SENT;
      return isAlive();
    }
    return false;
  }

  private boolean canReceivePacket(Packet packet)
  {
    if (isAlive())
    {
      m_energyRemaining -= packet.getSize() * ENERGY_PER_BYTE_RECEIVED;
      return isAlive();
    }
    return false;
  }

  public boolean isAlive()
  {
    if (m_timeOfDeath < 0 && m_energyRemaining <= 0)
    {
      m_timeOfDeath = Simulation.getTime();
    }
    return m_energyRemaining > 0;
  }

  public int getTimeOfDeath()
  {
    return m_timeOfDeath;
  }

  public int getEnergyConsumed()
  {
    return m_energyConsumed.get();
  }

  public Collection<Node> getNeighbors()
  {
    Set<Node> result = new HashSet<Node>();
    for (Link link : m_links)
    {
      for (Node node : link.getNodes())
      {
        result.add(node);
      }
    }
    result.remove(this);
    return result;
  }

  @Override
  public String toString()
  {
    return this.getClass().getSimpleName() + ": " + getAddress().toString();
  }

  public int getBytesReceived()
  {
    return m_bytesReceived.get();
  }

  public int getBytesSent()
  {
    return m_bytesSent.get();
  }

}
